package de.hauschild.gmltracer.tracer.impl;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;

import org.apache.commons.math.geometry.Vector3D;

import de.hauschild.gmltracer.tracer.Light;
import de.hauschild.gmltracer.tracer.Ray;
import de.hauschild.gmltracer.tracer.RayRenderer;
import de.hauschild.gmltracer.tracer.Renderer;
import de.hauschild.gmltracer.tracer.Shape;

public class OnTheFlyRenderer implements Renderer {

  private class OnTheFlyRendererFrame extends JFrame {

    private static final long serialVersionUID = -904527851777033441L;

    final BufferedImage image;
    private final JPanel panel;

    private Graphics2D imageGraphics;

    public OnTheFlyRendererFrame(final int width, final int height) {
      super();

      setDefaultCloseOperation(EXIT_ON_CLOSE);

      panel = new JPanel() {
        private static final long serialVersionUID = 8752656789254367791L;

        @Override
        public void paint(final Graphics g) {
          super.paint(g);
          g.clearRect(0, 0, getBounds().width, getBounds().height);
          g.drawImage(image, 0, 0, null);
        }
      };
      panel.setPreferredSize(new Dimension(width, height));
      add(panel);
      final Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
      setBounds((dim.width - width) / 2, (dim.height - height) / 2, width, height);
      setResizable(false);

      pack();

      addKeyListener(new KeyListener() {
        @Override
        public void keyPressed(final KeyEvent e) {
          if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            dispose();
          }
        }

        @Override
        public void keyReleased(final KeyEvent e) {
        }

        @Override
        public void keyTyped(final KeyEvent e) {
        }
      });

      image = new BufferedImage(800, 800, BufferedImage.TYPE_INT_RGB);

      setVisible(true);
    }

    public void drawPixel(final int x, final int y, final Color color) {
      if (imageGraphics == null) {
        imageGraphics = image.createGraphics();
      }
      imageGraphics.setColor(color);
      imageGraphics.drawLine(x, y, x, y);
    }
  }

  private static final RayRenderer SIMPLE_RAY_RENDERER = new SimpleRayRenderer();

  @Override
  public void render(final Vector3D ambientLightIntensity, final List<Light> lights, final Shape scene,
      final int traceDepth, final double fieldOfView, final int width, final int height, final String outputFileName) {
    final OnTheFlyRendererFrame frame = new OnTheFlyRendererFrame(width, height);

    new Runnable() {

      @SuppressWarnings("boxing")
      @Override
      public void run() {
        final double totalPixelCount = width * height;

        final long startTime = System.currentTimeMillis();

        for (int y = 0; y < height; y++) {
          // update frame title
          final double currentPixelCount = y * width;
          frame.setTitle(String.format("%s: %.2f", outputFileName, currentPixelCount / totalPixelCount * 100.0) + "%"); //$NON-NLS-1$ //$NON-NLS-2$

          for (int x = 0; x < width; x++) {
            renderPixel(ambientLightIntensity, lights, scene, fieldOfView, width, height, frame, y, x, traceDepth);
          }
        }

        // rendering complete
        frame.setTitle(String.format("%s: %.2fs", outputFileName, (System.currentTimeMillis() - startTime) / 1000.0));
      }

      private void renderPixel(final Vector3D ambientLightIntensity, final List<Light> lights, final Shape scene,
          final double fieldOfView, final int width, final int height, final OnTheFlyRendererFrame frame, final int y,
          final int x, final int tracingDepth) {
        // build the ray into the scene
        final Ray ray = RendererUtils.buildRay(x, y, fieldOfView, width, height);
        // render ray
        final Vector3D colorForRay = SIMPLE_RAY_RENDERER.renderRay(ray, scene, ambientLightIntensity, lights,
            traceDepth, 1);
        // draw the current pixel and repaint
        frame.drawPixel(x, y, RendererUtils.pointToColor(colorForRay));
        frame.repaint();
      }
    }.run();
  }
}
